using System;
using System.Collections.Generic;
using System.Linq;
using Arcane.Source.Client;
using Arcane.Source.Data;
using Arcane.Source.Utils;
using Godot;

namespace Arcane.Source
{
    public partial class GameOS : Node
    {
        [Export]
        Node _viewMount;
        [Export]
        Control _UIMount;
        [Export]
        Control _alertMount;
        [Export]
        Control _debugMount;
        [Export(PropertyHint.MultilineText)]
        string ScenesDir { get; set; } = null;

        private readonly Stack<IContext> _loadStack = [];
        private IContext _currentContext = null;
        public IContext CurrentContext => _currentContext;

        // View godot scene registry.
        private readonly Dictionary<string, PackedScene> _viewScenes = [];
        private IView _currentView = null;
        public IView CurrentView { get => _currentView; }

        public bool Alerting => _alertMount?.Visible == true;

        public EventStorageHub EventHub { get; private set; }

        public override void _Ready()
        {
            base._Ready();
            EventHub = new();
            if (!string.IsNullOrEmpty(ScenesDir))
            {
                RegisterViewsFromDir(ScenesDir);
            }
        }

        public override void _Notification(int what)
        {
            if (what == NotificationApplicationPaused)
            {
                _currentContext.OnPause();
            }
            else if (what == NotificationApplicationResumed)
            {
                _currentContext.OnResume(null);
            }
            else if (what == NotificationWMGoBackRequest && !_currentContext.PreventBack)
            {
                PopContext();
            }
        }

        public override void _PhysicsProcess(double delta)
        {
            _currentContext?.OnTick();
        }

        protected virtual void RequestQuit() => Quit();
        public void Quit() => GetTree().Quit();

        // Contexts are managed by stack, it represents the game logic.
        #region Context Management
        // Push a new context (with message), save previous context.
        public void PushContext(IContext context, Msg msg = null)
        {
            if (_currentContext != null)
            {
                _currentContext.OnPause();
                _loadStack.Push(_currentContext);
            }

            _currentContext = context;
            if (context == null)
            {
                GD.Print("Trying to push null context!");
                return;
            }

            _currentContext.Os = this;
            _currentContext.OnLoad(msg);
        }

        // Replace current context (with message) and drop last context.
        public void ReplaceContext(IContext context, Msg msg = null)
        {
            if (_currentContext != null)
            {
                _currentContext.OnUnload();
                _currentContext.Os = null;
            }

            _currentContext = context;
            if (context == null) return;

            _currentContext.Os = this;
            _currentContext.OnLoad(msg);
        }

        // Drop current context and restore (if exists) previous context (with message).
        public void PopContext(Msg msg)
        {
            // if thers is no context to restore, quit the game.
            if (_loadStack.Count <= 0)
            {
                RequestQuit();
                return;
            }

            if (_currentContext != null)
            {
                _currentContext.OnUnload();
                _currentContext.Os = null;
            }

            _currentContext = _loadStack.Pop();
            _currentContext.OnResume(msg);
        }

        // Godot interface.
        public void PopContext() => PopContext(null);
        #endregion

        // Views are always be only one current in GameOS.

        #region View Management
        // Replace current view to a new view in [viewName], 
        // returns a reference to new view.
        public IView LoadView(string viewName)
        {
            MountView(CreateView(viewName));
            return _currentView;
        }

        // Generic ver.
        public V LoadView<V>(string viewName) => (V)LoadView(viewName);

        // Mounts target view.
        public void MountView(IView view)
        {
            if (_currentView == view) return;

            UnmountView();

            _currentView = view;
            
            // Set custom background color.
            if (_currentView == null) return;
            if (_currentView.ClearColor is Color color)
                RenderingServer.SetDefaultClearColor(color);

            // Set pointer, trig OnMount event.
            _currentView.Os = this;
            _currentView.OnMount();

            // mount godot node to os.
            if (_currentView.Node != null)
                _viewMount.AddChild(_currentView.Node);

            // mount godot ui to os ui mnt.
            if (_currentView.UI != null)
            {
                _currentView.UI.GetParent()?.RemoveChild(_currentView.UI);
                _UIMount.AddChild(_currentView.UI);
            }
        }

        public void UnmountView()
        {
            if (_currentView == null) return;

            _currentView.OnUnmount();

            // Clear all references. (Without free memory) SideEffect: View's UI node becomes orphan.
            if (_currentView.Node != null) _viewMount.RemoveChild(_currentView.Node);
            if (_currentView.UI != null) _UIMount.RemoveChild(_currentView.UI);
            _currentView.Os = null;
            _currentView = null;
        }

        public bool TryUnmountView(IView view)
        {
            if (view == _currentView)
            {
                UnmountView();
                return true;
            }
            return false;
        }

        public void QueueFreeView()
        {
            if (_currentView == null) return;

            var t = _currentView;
            UnmountView();
            t.QueueFree();
        }

        public V ReplaceView<V>(IView view) where V: IView
        {
            if (_currentView == view) return (V)_currentView;

            QueueFreeView();

            _currentView = view;
            
            // Set custom background color.
            if (_currentView == null) return default;
            if (_currentView.ClearColor is Color color)
                RenderingServer.SetDefaultClearColor(color);

            // Set pointer, trig OnMount event.
            _currentView.Os = this;
            _currentView.OnMount();

            // mount godot node to os.
            if (_currentView.Node != null)
                _viewMount.AddChild(_currentView.Node);

            // mount godot ui to os ui mnt.
            if (_currentView.UI != null)
            {
                _currentView.UI.GetParent()?.RemoveChild(_currentView.UI);
                _UIMount.AddChild(_currentView.UI);
            }

            return (V)_currentView;
        }

        public V ReplaceView<V>(string name) where V : IView => ReplaceView<V>(CreateView(name));

        public void RegisterViewScene(string name, PackedScene viewScene)
        {
            _viewScenes.Add(name, viewScene);
        }

        public void RegisterViewScene(string name, string resPath) => RegisterViewScene(name, GD.Load<PackedScene>(resPath));
        public void RegisterViewsFromDir(string dirPath)
        {
            using var dir = DirAccess.Open(dirPath);

            if (dir == null)
            {
                GD.PrintErr("Failed to get dir:", dirPath);
                return;
            }

            foreach (var scnFile in dir.GetFiles().Where(file => file.EndsWith("scn")))
            {
                var name = scnFile.GetBaseName();
                RegisterViewScene(name, ResourceLoader.Load<PackedScene>(dirPath.PathJoin(scnFile), "PackedScene"));
            }
        }

        // Create a new view instance by registered view scene
        public IView CreateView(string name)
        {
            if (_viewScenes.TryGetValue(name, out var layout))
            {
                var view = layout.Instantiate() as IView;
                return view;
            }
            return null;
        }
        #endregion

        public void ShowAlert(Control alert)
        {
            if (_alertMount == null) return;

            _alertMount.AddChild(alert);
            _alertMount.Visible = true;
        }

        public void HideAlert()
        {
            if (_alertMount == null) return;

            foreach (var child in _alertMount.GetChildren())
            {
                _alertMount.RemoveChild(child);
            }
            _alertMount.Visible = false;
        }
    }
}